﻿//checked
using System.IO;
using System.Collections.Generic;
using net.entity.framework.common;

namespace net.entity.framework.query {
    public abstract class QueryClause : IQueryStateCompiler {
        protected enum ClassificationType {
            Indeterminate,
            DefinitelyFalse,
            DefinitelyTrue
        } ;

        #region And, Or, Not

        public static QueryClause operator |(QueryClause lhs, QueryClause rhs) {
            return OrClause.Create(Coding.MakeEnumerable(lhs, rhs));
        }

        public static QueryClause operator &(QueryClause lhs, QueryClause rhs) {
            return AndClause.Create(Coding.MakeEnumerable(lhs, rhs));
        }

        public static QueryClause operator !(QueryClause queryClause) {
            return NotClause.Create(queryClause);
        }

        #endregion

        public static bool operator true(QueryClause item) {
            return item.Classification == ClassificationType.DefinitelyTrue;
        }

        public static bool operator false(QueryClause item) {
            return item.Classification == ClassificationType.DefinitelyFalse;
        }

        /// <summary>
        /// This is to initialize the booleanClause with true / false
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator QueryClause(bool value) {
            return BooleanConstant.Create(value);
        }

        public bool TryGetConstant(out bool value) {
            var classification = Classification;
            if (classification == ClassificationType.Indeterminate) {
                value = false;
                return false;
            }
            value = classification == ClassificationType.DefinitelyTrue;
            return true;
        }

        public bool IsConstantTrue {
            get { return Classification == ClassificationType.DefinitelyTrue; }
        }

        public bool IsConstantFalse {
            get { return Classification == ClassificationType.DefinitelyFalse; }
        }

        protected virtual ClassificationType Classification {
            get { return ClassificationType.Indeterminate; }
        }

        //note: not sure what does this thing does?
        //public virtual bool TryNegateForQueryOptimization(out QueryClause result) {
        //    result = null;
        //    return false;
        //}

        public virtual IEnumerable<QueryClause> YieldAndSubexpressionsOrSelf() {
            yield return this;
        }

        public virtual IEnumerable<QueryClause> YieldOrSubexpressionsOrSelf() {
            yield return this;
        }

        public abstract void CompileState(CompiledQueryState state);

        public abstract void EmitQuery(TextWriter writer, CompiledQueryState state);
    }

    public sealed class NotClause : QueryClause {
        #region Members

        private readonly QueryClause subQueryClause;

        #endregion

        #region Constructors/Create

        private NotClause(QueryClause subQueryClause) {
            this.subQueryClause = subQueryClause;
        }

        public static QueryClause Create(QueryClause queryClause) {
            return new NotClause(queryClause);
            //QueryClause result;
            //return queryClause.TryNegateForQueryOptimization(out result) ? result : new NotClause(queryClause);
        }

        #endregion

        #region Methods

        //public override bool TryNegateForQueryOptimization(out QueryClause result) {
        //    result = subQueryClause;
        //    return true;
        //}

        public override void CompileState(CompiledQueryState state) {
            subQueryClause.CompileState(state);
        }

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            writer.Write("(NOT ");
            subQueryClause.EmitQuery(writer, state);
            writer.Write(")");
        }

        #endregion
    }

    public sealed class BooleanConstant : QueryClause {
        #region Members

        public static readonly BooleanConstant True = new BooleanConstant(ClassificationType.DefinitelyTrue, "1=1");
        public static readonly BooleanConstant False = new BooleanConstant(ClassificationType.DefinitelyFalse, "1=0");

        private readonly ClassificationType classification;
        private readonly string sqlExpansion;

        #endregion

        #region Constructor/Create

        private BooleanConstant(ClassificationType classification, string sqlExpansion) {
            this.classification = classification;
            this.sqlExpansion = sqlExpansion;
        }

        public static BooleanConstant Create(bool value) {
            return value ? True : False;
        }

        #endregion

        #region Properties

        protected override ClassificationType Classification {
            get { return classification; }
        }

        #endregion

        #region Methods

        public override void CompileState(CompiledQueryState state) {}

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            writer.Write(sqlExpansion, state);
        }

        #endregion
    }

    public abstract class ConditionalBooleanClause : QueryClause {
        #region Members

        private readonly Operations operations;
        protected readonly IEnumerable<QueryClause> subExpressions;

        #endregion

        #region Constructors

        protected ConditionalBooleanClause(Operations operations, IEnumerable<QueryClause> subExpressions) {
            this.operations = operations;
            this.subExpressions = subExpressions;
        }

        #endregion

        #region Methods

        public override void CompileState(CompiledQueryState state) {
            foreach (var subExpression in subExpressions) {
                operations.CompileState(state);
                subExpression.CompileState(state);
            }
        }

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            var index = 0;
            foreach (var subExpression in subExpressions) {
                if (index == 0) {
                    writer.Write("(");
                    index++;
                } else {
                    operations.EmitQuery(writer, state);
                }
                subExpression.EmitQuery(writer, state);
            }
            writer.Write(")");
        }

        #endregion
    }

    public sealed class AndClause : ConditionalBooleanClause {
        #region Constructors/Create

        private AndClause(IEnumerable<QueryClause> items) : base(Operations.And, items) {}

        /// <summary>
        /// This is pretty ineresting, it resolves the expression as it builds. 
        /// Essentially does one level of optimization: If there is any false constant 
        /// then return the sql as false
        /// </summary>
        /// <param name="subExpressions"></param>
        /// <returns></returns>
        public static QueryClause Create(IEnumerable<QueryClause> subExpressions) {
            if (subExpressions.IsEmpty()) {
                return BooleanConstant.True;
                //AND(empty list) is defined to be true ("are they all true? degenerately, yes")
            }
            var compressedList = new List<QueryClause>();
            foreach (var subExpression in subExpressions) {
                foreach (var expandedExpression in subExpression.YieldAndSubexpressionsOrSelf()) {
                    bool value;
                    if (!expandedExpression.TryGetConstant(out value)) {
                        //nonconstant means keep it
                        compressedList.Add(expandedExpression);
                    } else if (!value) {
                        //false means force the whole thing to false
                        return BooleanConstant.False;
                    } //true means drop it
                }
            }
            //if we end up with an empty list, we must have squished out a bunch of TRUE's
            return compressedList.Count == 0 ? (QueryClause) BooleanConstant.True : new AndClause(compressedList);
        }

        #endregion

        #region Methods

        public override IEnumerable<QueryClause> YieldAndSubexpressionsOrSelf() {
            foreach (var subExpression in subExpressions) {
                yield return subExpression;
            }
        }

        #endregion
    }

    public sealed class OrClause : ConditionalBooleanClause {
        #region Constructors/Create

        private OrClause(IEnumerable<QueryClause> items) : base(Operations.Or, items) {}

        /// <summary>
        /// This is pretty ineresting, it resolves the expression as it builds. 
        /// Essentially does one level of optimization: If there is any true constant 
        /// then return the sql as false
        /// </summary>
        /// <param name="subExpressions"></param>
        /// <returns></returns>
        public static QueryClause Create(IEnumerable<QueryClause> subExpressions) {
            if (subExpressions.IsEmpty()) {
                return BooleanConstant.False;
                //OR(empty list) is defined to be false ("are they all true? degenerately, no")
            }
            var compressedList = new List<QueryClause>();
            foreach (var subExpression in subExpressions) {
                foreach (var expandedExpression in subExpression.YieldOrSubexpressionsOrSelf()) {
                    bool value;
                    if (!expandedExpression.TryGetConstant(out value)) {
                        compressedList.Add(expandedExpression); //nonconstant means keep it
                    } else if (value) {
                        return BooleanConstant.True; //true means force the whole thing to true
                    } //false means drop it
                }
            }
            //if we end up with an empty list, we must have squished out a bunch of FALSE's
            return compressedList.Count == 0 ? (QueryClause) BooleanConstant.False : new OrClause(compressedList);
        }

        #endregion

        #region Methods

        public override IEnumerable<QueryClause> YieldOrSubexpressionsOrSelf() {
            foreach (var subExpression in subExpressions) {
                yield return subExpression;
            }
        }

        #endregion
    }

    public abstract class RelationalClause : QueryClause {
        #region Members

        protected readonly Operations operations;
        protected readonly Expression lhs;
        protected readonly Expression rhs;

        #endregion

        #region Constructors

        protected RelationalClause(Operations operations, Expression lhs, Expression rhs) {
            this.operations = operations;
            this.lhs = lhs;
            this.rhs = rhs;
        }

        #endregion

        #region Methods

        public override void CompileState(CompiledQueryState state) {
            lhs.CompileState(state);
            rhs.CompileState(state);
        }

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            writer.Write("(");
            lhs.EmitQuery(writer, state);
            writer.Write(" ");
            operations.EmitQuery(writer, state);
            writer.Write(" ");
            rhs.EmitQuery(writer, state);
            writer.Write(")");
        }

        #endregion
    }

    public sealed class LikeClause : RelationalClause {
        #region Constructors/Create

        private LikeClause(Expression lhs, Expression rhs) : base(Operations.Like, lhs, rhs) {}

        public static QueryClause Create(Expression lhs, Expression rhs) {
            object value;
            if (rhs.TryGetConstant(out value) && value.ToString() == "%") {
                return BooleanConstant.True;
            }
            return new LikeClause(lhs, rhs);
        }

        #endregion
    }

    public sealed class BetweenClause : RelationalClause {
        #region Members

        private readonly Expression item;

        #endregion

        #region Constructors/Create

        private BetweenClause(Expression item, Expression lhs, Expression rhs)
            : base(Operations.Between, lhs, rhs) {
            this.item = item;
        }

        public static BetweenClause Create(Expression item, Expression lhs, Expression rhs) {
            return new BetweenClause(item, lhs, rhs);
        }

        #endregion

        #region Methods

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            writer.Write("(");
            item.EmitQuery(writer, state);
            writer.Write(" BETWEEN ");
            lhs.EmitQuery(writer, state);
            writer.Write(" AND ");
            rhs.EmitQuery(writer, state);
            writer.Write(")");
        }

        #endregion
    }

    public sealed class ComparisonClause : RelationalClause {
        #region Constructors/Create

        private ComparisonClause(Operations operations, Expression lhs, Expression rhs) : base(operations, lhs, rhs) {}

        public static QueryClause Create(Operations operations, Expression lhs, Expression rhs) {
            return new ComparisonClause(operations, lhs, rhs);
        }

        #endregion
    }
}